/*!
// Testweb v0.3.0
// http://or-change.cn
// Copyright 2014 OrChange Inc. All rights reserved.
// Licensed under the GPL License.
*/
/*jslint vars: true, forin: true, plusplus: true */
/*global define: false */

/**
 * This is template view engine, including the constructor of  the template and
 * template View Pool Management.
 * tpm opens four methods like open、close、refresh、getView to outside
 * @module controller
 * @requires config
 * @requires $
 * @requires Template
 * @example
 *
 */
define(function (require, exports, module) {
	'use strict';
	var config = require("./configure");
	var $ = require("lib/jquery/jquery");

	// 获取路径配置
	var url = config.get("C_SERVER") + config.get("C_SCRIPT_PATH");
	var cur_lang = config.get("C_LANGUAGE");
	var C_NO_BOX_MODE = config.get("C_NO_BOX_MODE");
	var C_CROSS_DOMAIN = config.get("C_CROSS_DOMAIN");
	var C_VIEW_JSONP_API = config.get("C_VIEW_JSONP_API");
	var C_LANG_JSONP_API = config.get("C_LANG_JSONP_API");
	$.ajaxSetup({
		jsonp: config.get("C_JSONP")
	});
	if (C_CROSS_DOMAIN) {
		$.ajaxSetup({
			dataType: "jsonp"
		});
	}

	// 相关正则
	var view_box_g_r = /\{\{#box\:\w+\}\}/g,
		view_box_r = /\{\{#box\:(\w+)\}\}/,
		holder_r = /holder="(\w+)"/,
		root_element_r = /(<\w+)/,
		hash_url_r = /(\w+)(\/(\w+))?(\?([\w=&]+))?/,
		hash_get_r = /(\w+)=(\w+)/g;


	var View, vm, isRefreshing = false;

	/**
	 * 模板的构造函数，管理模板不同阶段的缓存，激活状态
	 * 数据，模板原文，和渲染完毕的回调。
	 *
	 * @class View
	 * @constructor
	 * @param options {Object} 构造配置
	 */
	View = function View(options) {
		// 处理模板原文中的容器
		var raw, holder_match;
		raw = options.raw.replace(view_box_g_r, function (box) {
			var box_id = box.match(view_box_r)[1];
			return '<div class="view-box" id="' + box_id + '"></div>';
		}).replace(root_element_r, "$1 tpl=\"" + options.name + "\"");
		holder_match = raw.match(holder_r);

		/**
		 * The name of the view. Filename of "View/[name]_template.html",
		 * "Controller/[name].js" and attribute "tpl" of the root element
		 * in a view must be same!
		 *
		 * @property name
		 * @type string
		 */
		this.name = options.name;
		/**
		 * #模板的最初原文，作为ajax的response被第一次构建，这里用于缓存初始原文
		 * 使用jsRender框架
		 *
		 * @property response
		 * @type string
		 */
		this.template = $.templates(raw);
		/**
		 * #模板的数据，有指定的格式，静态的模板可以在构造时初始化
		 * data在原型方法里有指定的输入方法
		 * data也是View模型的缓存
		 *
		 * @property data
		 * @type object
		 */
		this.data = options.data || {};
		/**
		 * #模板在渲染和重渲染之后需要执行的用户自定义的函数内容，不会在构造阶
		 * 段调用controller实际上是View的控制器
		 *
		 * @property controller
		 * @type function
		 */
		this.controller = options.controller || this.noneController;
		/**
		 * 视图的激活方法，在视图渲染前调用，用于与处理数据、接受表单输入等
		 *
		 * @property methods
		 * @type Object
		 */
		this.methods = options.methods || {};

		/**
		 * 模板的激活状态
		 *
		 * @property active
		 * @type boolean
		 */
		this.active = false;
		/**
		 * 模板的插入容器，如果没有指定holder说明与name同名
		 *
		 * @property name
		 * @type string
		 */
		this.holder = holder_match ? holder_match[1] : options.name;
		/**
		 * 计时器管理
		 *
		 * @property timers
		 * @type object
		 */
		this.timers = {
			timeout: [],
			interval: []
		};

		/**
		 * 父级视图
		 *
		 * @property parent
		 * @type View|null
		 */
		this.parent = null;
		/**
		 * 子视图
		 *
		 * @property children
		 * @type Array
		 */
		this.children = [];
		/**
		 * 预加载列表
		 *
		 * @property pre_load
		 * @type Array|undefined
		 */
		this.pre_load = options.pre_load || undefined;
	};
	View.prototype.$render = function () {
		var box_$, conflict_name, view_html, that = this;

		// 渲染innerHTML字符串
		view_html = this.template.render(this.data);

		// 装载到合法的view-box里
		// 1.寻找合法的view-box
		box_$ = $(".view-box#" + this.holder);
		if (box_$.length === 1) {
			box_$.html(view_html);
			$("[tpl=" + this.name + "]").unwrap();

		} else {
			// 2.搜索冲突的view-box
			conflict_name = $("[holder=" + this.holder + "]").attr("tpl");
			if (conflict_name) {
				vm.deactivateView(conflict_name);
				$(".view-box#" + this.holder).html(view_html);
				$("[tpl=" + this.name + "]").unwrap();

			} else if (C_NO_BOX_MODE === "append") {
				// 3.无任何合法的view-box 有“追加<body>模式”和“无动作模式”
				this.no_box = true;
				$("body").append(view_html);
			}
		}

		// 构建视图树
		var parent_name, parent_$;
		parent_$ = $("[tpl=" + this.name + "]").parents("[tpl]");
		if (parent_$.length) {
			parent_name = parent_$.first().attr("tpl");
			this.parent = {
				view: vm.getView(parent_name)
			};
			this.parent.index = this.parent.view.children.push(this) - 1;
		} else {
			this.parent = null;
		}

		this.element = $("[tpl=" + this.name + "]").get(0);

		return this;
	};
	View.prototype.$hashRouter = function () {
		// 重写hash路由
		var that = this;
		$("form[action^=#]", this.element).submit(function (event) {
			event.preventDefault();
			var form, action, form_$ = $(this);

			action = form_$.attr("action");
			if (form_$.attr("method") === "post") {
				form = $(this).serializeObject();
				that.deactiveMe();
				var hash_match = action.match(hash_url_r);
				var view_name = hash_match[1],
					method_name = hash_match[3];

				vm.activateView(view_name, {
					$_POST: form
				}, method_name);
			} else {
				form = form_$.serialize();
				that.deactiveMe();

				window.document.location.hash = action + "?" + form;
			}
		});

		return this;
	};
	View.prototype.$callMethod = function (method_name) {
		this.method = method_name;
		if (this.methods.hasOwnProperty("$default")) {
			this.methods.$default.call(this);
		}
		if (method_name && this.methods.hasOwnProperty(method_name)) {
			this.methods[method_name].call(this);
		}

		return this;
	};
	/**
	 * 清除本模板的所有计时器
	 *
	 * @param time {number} 时间长度
	 * @param callback {function} 执行的过程
	 * @chainable
	 */
	View.prototype.$clearAllTimer = function () {
		while (this.timers.timeout.length) {
			clearTimeout(this.timers.timeout.pop());
		}
		while (this.timers.interval.length) {
			clearInterval(this.timers.interval.pop());
		}
		return this;
	};
	/**
	 * 激活这个模板：清空旧的计时器；刷新激活标识；切换语言；渲染出innerHTML并
	 * 追加到<body>；执行绑定的模板控制器回调函数。
	 * 注：在激活方法里执行清理计时器操作是为了refresh和open操作时都能正确触发。
	 *
	 * @param [data] {object} 待刷新的数据
	 * @chainable
	 */
	View.prototype.activeMe = function (data, method, callback) {
		// 处理预加载
		if (this.pre_load instanceof Array) {
			while (this.pre_load.length) {
				vm.cacheView(this.pre_load.pop());
			}
			delete this.pre_load;
		}

		// 清空计时器 设置激活 切换语言 设置数据 html填充 绑定哈希路由 绑定控制器
		this.$clearAllTimer().setData(data || null).$callMethod(method);
		if (this.block !== true) {
			this.setActive().switchLanguage()
				.$render().$hashRouter()
				.controller.call(this.element, this);
		} else {
			delete this.block;
		}
		if (typeof callback === "function") {
			callback.call();
		}
		return this;
	};
	/**
	 * 使当前模板自己失效
	 *
	 * @param [data] {object} 待刷新的数据
	 * @chainable
	 */
	View.prototype.deactiveMe = function ($isFast) {
		if (!this.isActive) {
			return this;
		}

		// 快速模式关闭子视图，因为子视图的HTML已经随本次卸载操作给 remove掉了
		// 所有的视图都要逐一关闭自己的子视图
		// 注意：这里使用了pop哦，所以children的数组会自动在完成关闭后清空
		var view;
		while (this.children.length && (view = this.children.pop())) {
			view.deactiveMe(true);
		}
		this.setInactive().$clearAllTimer();

		// 卸载视图的操作本身需要将自己在父级视图的children列表中删除，但子视图
		// children已经被全部清空关系，所以再将它们的parent清空即可
		if ($isFast === true) {
			this.parent = null;
			delete this.no_box;
			return this;
		}
		// --- 快速关闭方法截止到这里 ---//

		// 非快速模式要卸载HTML，还原占位dom
		if (this.no_box !== true) {
			$("[tpl=" + this.name + "]").wrap("<div id=\"" + this.holder +
				"\" class=\"view-box\"></div>").remove();
		} else {
			$("[tpl=" + this.name + "]").remove();
			delete this.no_box;
		}

		// 清除视图渲染树
		if (this.parent !== null) {
			this.parent.view.children[this.parent.index] = null;
			this.parent = null;
		}

		return this;
	};
	/**
	 * 刷新自己，说白了就是失效自己再激活
	 *
	 * @param [data] {object} 待刷新的数据
	 * @chainable
	 */
	View.prototype.refreshMe = function (data, method) {
		this.deactiveMe().activeMe(data, method);

		return this;
	};
	/**
	 * 设置受模板管理的超时计时器
	 *
	 * @param time {number} 时间长度
	 * @param callback {function} 执行的过程
	 * @chainable
	 */
	View.prototype.setTimeout = function (time, callback) {
		var id = setTimeout(callback, time);
		this.timers.timeout.push(id);

		return id;
	};
	/**
	 * 设置受模板管理的循环计时器
	 *
	 * @param time {number} 时间长度
	 * @param callback {function} 执行的过程
	 * @chainable
	 */
	View.prototype.setInterval = function (time, callback) {
		var id = setInterval(callback, time);
		this.timers.interval.push(id);

		return id;
	};
	/**
	 * 无callback的时候指向该原型
	 *
	 * @method noneCallback
	 * @returns {function} 空函数
	 */
	View.prototype.noneController = function () {
		return;
	};
	View.prototype.switchLanguage = function () {
		this.data.lang = vm.lang_packs[cur_lang];

		return this;
	};
	/**
	 * 判断模板是不是激活状态
	 *
	 * @method isActive
	 * @returns {boolean} 激活为true，非激活为false
	 */
	View.prototype.isActive = function () {
		return this.active === true;
	};
	/**
	 * 将该模板置于激活状态
	 *
	 * @method setActive
	 * @chainable
	 */
	View.prototype.setActive = function () {
		this.active = true;
		return this;
	};
	/**
	 * 将该模板置于非激活状态
	 *
	 * @method setInactive
	 * @chainable
	 */
	View.prototype.setInactive = function () {
		this.active = false;
		return this;
	};
	/**
	 * 将数据追加进模板的data中
	 *
	 * @method setData
	 * @param data {object} 需要被追加的数据
	 * @chainable
	 */
	View.prototype.setData = function (data) {
		if (typeof data === "object") {
			var data_key;
			for (data_key in data) {
				this.data[data_key] = data[data_key];
			}
		}

		return this;
	};
	View.prototype.redirect = function (hash) {
		location.hash = "";
		location.hash = hash;
		return this;
	};
	/**
	 * 从模板的data查询指定键值的数据
	 *
	 * @method getData
	 * @param key {string} 目标键值
	 * @returns {string|number|array|boolean} 对应的数据值
	 */
	View.prototype.getData = function (key) {
		if (this.data.hasOwnProperty(key)) {
			return this.data[key];
		}
		return false;
	};
	View.prototype.getRefreshQueue = function () {
		return this.$getChildren([]);
	};
	View.prototype.$getChildren = function ($tree) {
		if (this.children.length === 0) {
			$tree.push(this);
			return $tree;
		} else {
			var i, len = this.children.length;
			$tree.push(this);
			for (i = 0; i < len; i++) {
				if (this.children[i] !== null) {
					this.children[i].$getChildren($tree);
				}
			}
		}
		return $tree;
	};
	View.prototype.isRoot = function () {
		if (this.isActive() && this.parent === null) {
			return true;
		}
		return false;
	};
	View.prototype.setBlock = function () {
		this.block = true;

		return this;
	};

	/**
	 * Template View Management.
	 * The task of template view management is managing templte view,
	 * including acquisition、assembly、confirming status.etc.
	 * @class vm
	 * @final
	 */
	window.vm = vm = {
		/**
		 * template pool
		 * @property pool
		 * @type object
		 */
		pool: {},
		lang_packs: {}
	};
	/**
	 * Cache a new template to pool.分为两步：获取原文和获取控制器
	 * 因为这里用到了异步操作，所以用回调模式在创建完视图后执行callback
	 *
	 * @method cacheView
	 * @param view_name {String} 视图的名称
	 * @param callback {function} 缓存成功后的回调
	 */
	vm.cacheView = function (view_name, callback) {
		var options = {
			name: view_name
		};

		var ajax = {
			url: url + "View/" + view_name + "_template.html",
			success: function (template_raw) {
				options.raw = (typeof template_raw === "string") ? template_raw : template_raw.raw;
				require.async("controller/" + view_name, function (loader) {
					// 远程获取控制器的加载对象
					var $view, S$;
					if (loader !== null) {
						// 安全的jQuery函数
						S$ = function (path, context) {
							return $(path, context || $("[tpl=" + view_name + "]").get(0));
						};
						options.controller = loader.execute(S$, exports);
						options.pre_load = loader.preLoad;
						options.methods = loader.methods;
					}
					$view = vm.pool[view_name] = new View(options);

					if (typeof callback === "function") {
						callback.call(vm, $view);
					}
				});
			}
		};
		if (C_CROSS_DOMAIN) {
			ajax.url = C_VIEW_JSONP_API + view_name;
		}
		$.ajax(ajax);
	};
	/**
	 * Cache a new language package.
	 * 语言包必须被正确的阻塞式的加载完，因为该操作很重要，但很少被调用
	 * @method cacheLang
	 * @param lang_name {string}
	 * @param callback {function}
	 */
	vm.cacheLang = function (lang_name) {
		var ajax = {
			async: false,
			url: url + "Language/" + lang_name + ".json",
			success: function (lang_packs) {
				vm.lang_packs[lang_name] = lang_packs;
			}
		};
		if (C_CROSS_DOMAIN) {
			ajax.url = C_LANG_JSONP_API + lang_name;
		}
		$.ajax(ajax);
	};
	/**
	 * Check whether the template is cached or not according the template_name.
	 * @method isTemplateCached
	 * @param template_name {string}
	 * @returns {boolean}
	 */
	vm.isViewCached = function (view_name) {
		return this.pool.hasOwnProperty(view_name);
	};
	/**
	 * Check whether the language package is cached or not according the lang_name.
	 * @method isLanguageCached
	 * @param lang_name {string}
	 * @returns {boolean}
	 */
	vm.isLanguageCached = function (lang_name) {
		return this.lang_packs.hasOwnProperty(lang_name);
	};
	/**
	 * Activate template. That is important method of MVC layer. It checks the caching
	 * conditions of language and template.Calling activated method of template when
	 * everything is ready.
	 * @method activateView
	 * @param hash_url {string}
	 * @param [data] {object}
	 */
	vm.activateView = function (view, data, method_name, callback) {
		if (typeof view === "string") {
			// 已经激活的就没有任何动作
			// 断言：没有缓存的一定没激活
			if (this.isViewCached(view) && this.pool[view].isActive()) {
				return false;
			}

			// 检查语言包是不是存在，没有缓存就加载
			if (!this.isLanguageCached(cur_lang)) {
				this.cacheLang(cur_lang);
			}

			// 检查视图是不是已经缓存，没有缓存则加载
			if (!this.isViewCached(view)) {
				this.cacheView(view, function (view) {
					view.activeMe(data, method_name, callback);
				});
			} else {
				this.pool[view].activeMe(data, method_name, callback);
			}
		} else {
			view.activeMe(data, method_name, callback);
		}

		return this;
	};
	/**
	 * Refresh specified template.
	 * @method refresh
	 * @param template_name {string}
	 * @param [data] {object}
	 */
	vm.refreshView = function (view, data, method_name) {
		isRefreshing = true;
		// 获得遍历顺序 - 数组
		view = (typeof view === "string") ? this.pool[view] : view;
		var queue = view.getRefreshQueue();

		// 按序刷新视图
		queue.shift().refreshMe(data, method_name);
		while (queue.length) {
			queue.shift().refreshMe();
		}
		isRefreshing = false;

		return this;
	};
	/**
	 * Deactivate template.
	 * @method deactivateView
	 * @param template_name {string}
	 * @return {Template}
	 */
	vm.deactivateView = function (view) {
		view = (typeof view === "string") ? this.pool[view] : view;
		return view.deactiveMe();
	};
	/**
	 * Refresh all templates.
	 * @method refreshAll
	 * @return vm {object}
	 */
	vm.refreshAllView = function () {
		var views = this.getActiveRootViews();

		while (views.length) {
			this.refreshView(views.pop());
		}

		return this;
	};
	vm.getActiveRootViews = function () {
		var view, name, views = [];
		for (name in this.pool) {
			view = this.pool[name];
			if (view.isRoot()) {
				views.push(name);
			}
		}

		return views;
	};
	vm.getView = function (view_name) {
		return this.pool[view_name];
	};

	exports.open = function (view, data, method, callback) {
		if (!isRefreshing) {
			vm.activateView.apply(vm, arguments);
		}

		return this;
	};
	exports.close = function (view) {
		if (!isRefreshing) {
			vm.deactivateView(view);
		}

		return this;
	};
	exports.refresh = function (view, data, method) {
		if (!isRefreshing) {
			if (arguments.length) {
				vm.refreshView.apply(vm, arguments);
			} else {
				vm.refreshAllView();
			}
		}

		return this;
	};
	exports.getView = function (view_name) {
		return vm.pool[view_name];
	};

	window.V = exports;

	$(document).ready(function () {
		// 改变html的lang
		$("html").attr("lang", config.get("C_HTML_LANG"));
		// 开始第一个视图
		exports.open(config.get("C_DEFAULT_VIEW"));
		// 处理hash和hashGet
		$(window).hashchange(function (event) {
			var hash_match = location.hash.match(hash_url_r);
			if (hash_match !== null) {
				var isActive, param, data = {
						$_GET: {}
					},
					view = hash_match[1],
					method = hash_match[3],
					hash_get = hash_match[5];

				// 处理hashget
				while (param = hash_get_r.exec(hash_get)) {
					data.$_GET[param[1]] = param[2];
				}

				isActive = vm.isViewCached(view) && vm.pool[view].isActive();
				exports[isActive ? "refresh" : "open"](view, data, method);
			}
		});
	});
});
